﻿///////////////////////////////////////////////////////////////////////////////
//
// This file is part of the BassDrop project
//   https://code.google.com/p/bass-drop/
//
//
// Creator:	Gergely Krajcsovszki (krajcsovszkig@hotmail.com)
// Date:	2013-08-04 15:08
//
//
// Copyright © 2013-2014. Gergely Krajcsovszki
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
///////////////////////////////////////////////////////////////////////////////
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BassDrop.Core.Common.Localization;
using BassDrop.Core.Common.Exceptions;
using BassDrop.Core.Common.Utilities;

namespace BassDrop.Core.Common.Cloud.Assets
{
	/// <summary>
	/// Contains information about a file lock
	/// </summary>
	public struct LockInfo
	{
		/// <summary>
		/// The type of a lock
		/// </summary>
		public enum LockType
		{
			/// <summary>
			/// Allows reading the file
			/// </summary>
			Shared,
			/// <summary>
			/// Does not allow accessing the file in any way
			/// </summary>
			Exclusive
		}

		/// <summary>
		/// The device ID of the device owning the lock.
		/// If it is null, there is no lock.
		/// </summary>
		public string Owner
		{
			get;
			private set;
		}

		/// <summary>
		/// The ID of this lock
		/// </summary>
		public HashSet<ulong> LockIds
		{
			get;
			set;
		}

		/// <summary>
		/// The vector clock state when the lock was issued
		/// </summary>
		public VectorClock Issued
		{
			get;
			private set;
		}

		/// <summary>
		/// The UTC date and time the lock expires in the time of the issuer
		/// </summary>
		private DateTime Expires
		{
			get;
			set;
		}

		/// <summary>
		/// The type of this lock
		/// </summary>
		public LockType Type
		{
			get;
			private set;
		}

		private List<LockInfo> m_sharedWith;

		/// <summary>
		/// The list of locks this one is shared with, can be set by outside methods, only for information
		/// </summary>
		public List<LockInfo> SharedWith
		{
			get
			{
				if ( m_sharedWith == null )
				{
					m_sharedWith = new List<LockInfo>();
				}

				return m_sharedWith;
			}

			set
			{
				m_sharedWith = value;
			}
		}

		/// <summary>
		/// The path of the lock file in the cloud if known
		/// </summary>
		public string Path
		{
			get;
			set;
		}

		/// <summary>
		/// Constructs a new LockInfo
		/// </summary>
		/// <param name="owner">the ClientID of the owner of the lock</param>
		/// <param name="lockIds">the IDs of this lock</param>
		/// <param name="type">the type of the lock (shared or exclusive)</param>
		/// <param name="issued">the state of the vector clock at the time of the lock's creation</param>
		/// <param name="expires">the UTC timestamp of the expiration of the lock in the owner's time</param>
		public LockInfo( string owner, HashSet<ulong> lockIds, LockType type, VectorClock issued, DateTime expires )
			: this()
		{
			Owner = owner;
			LockIds = lockIds;
			Type = type;
			Issued = issued;
			Expires = expires;
		}

		/// <summary>
		/// Gets the duration in which the lock is valid from the time of the request in milliseconds.
		/// If it equals 0, the lock is expired.
		/// </summary>
		/// <param name="reference">the time to use as a reference, DateTime.UtcNow for local times and the estimated current UTC time for remote times</param>
		public long GetValidFor( DateTime reference )
		{
			long millis = TimeUtils.TimeSpanToMillis( Expires - reference );
			return millis > 0 ? millis : 0;
		}

		/// <summary>
		/// Postpones the expiration of this lock until the given DateTime. If the given date is no later
		/// than the current expiration date, nothing is changed
		/// </summary>
		/// <param name="expires"></param>
		public void EnsureValidUntil( DateTime expires )
		{
			if ( expires > Expires )
			{
				Expires = expires;
			}
		}

		/// <summary>
		/// Parses the lock info from the contents of a lock file passed as string
		/// </summary>
		/// <param name="text">the contents of a lock file in a string</param>
		/// <returns>the LockInfo parsed from the text</returns>
		public static LockInfo Parse( string text )
		{
			LockInfo info = new LockInfo();

			try
			{
				foreach ( var line in text.Split( '\n' ) )
				{
					string[] tokens = line.Split( '=' );

					if ( tokens[0] == "owner" )
					{
						info.Owner = tokens[1];
					}
					else if ( tokens[0] == "lockids" )
					{
						info.LockIds = new HashSet<ulong>( tokens[1].Split( ',' ).Select( ulong.Parse ) );
					}
					else if ( tokens[0] == "issued" )
					{
						info.Issued = VectorClock.Parse( tokens[1] );
					}
					else if ( tokens[0] == "expires" )
					{
						info.Expires = DateTime.Parse( tokens[1] );
					}
					else if ( tokens[0] == "type" )
					{
						info.Type = (LockType) Enum.Parse( info.Type.GetType(), tokens[1], true );
					}
				}
			}
			catch ( Exception e )
			{
				throw new DataFormatException( Strings.ExceptionBadLockfile, e );
			}

			return info;
		}

		/// <summary>
		/// Converts this lock info to text that can be saved in a file and later parsed
		/// </summary>
		/// <returns></returns>
		public string ToLockFileText()
		{
			StringBuilder builder = new StringBuilder();

			builder.Append( "owner=" );
			builder.Append( Owner );
			builder.Append( '\n' );

			builder.Append( "lockids=" );
			builder.Append( string.Join( ",", LockIds ) );
			builder.Append( '\n' );

			builder.Append( "issued=" );
			builder.Append( Issued.ToVectorClockText() );
			builder.Append( '\n' );

			builder.Append( "expires=" );
			builder.Append( Expires.ToString( "o" ) );
			builder.Append( '\n' );

			builder.Append( "type=" );
			builder.Append( Type.ToString() );

			return builder.ToString();
		}
	}
}
